﻿using System.Collections.Generic;
using System.Linq;
using Notoric.Commanding.CommandBase;
using Notoric.Commanding;

namespace Notoric.ViewModel
{
    /// <summary>
    /// An undoable command for removing a core symbol.
    /// </summary>
    public class RemoveCoreSymbolCommand : UndoableCommandBase
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="RemoveCoreSymbolCommand"/> class.
        /// </summary>
        /// <param name="undoStack">The undo command stack</param>
        /// <param name="redoStack">The redo command stack</param>
        public RemoveCoreSymbolCommand(CommandStack undoStack, CommandStack redoStack)
            : base(undoStack, redoStack)
        {
            AddParameter<Elements.CWNotation.CoreSymbol>("Symbol", ParameterDirection.In);

            Name = "Remove Symbol";

            removedSymbols = new List<Elements.CWNotation.CoreSymbol>();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Stores the core symbols that will be removed after the removal of durational symbol element presented by the <see cref="Symbol"/>.
        /// </summary>
        /// <param name="removedElement">
        /// <para>
        /// Type: <see cref="Model.Elements.DurationalSymbolElement"/>
        /// </para>
        /// <para>
        /// The removed element.
        /// </para>
        /// </param>
        /// <param name="project">
        /// <para>
        /// Type: <see cref="Elements.Project"/>
        /// </para>
        /// <para>
        /// The project.
        /// </para>
        /// </param>
        private void StoreRemovedSymbols(Model.Elements.DurationalSymbolElement removedElement, Elements.Project project)
        {
            var modelComposition = removedElement.FindOwner<Model.Elements.CompositionElement>();
            var measure = removedElement.FindOwner<Model.Elements.MeasureElement>();
            var staffDescriptor = ((Model.Elements.CWNotation.Staff)Symbol.FindOwner<Elements.CWNotation.Staff>().AdaptedElement).Descriptor;
            var references = modelComposition.GetReferencingPages(measure, measure);

            foreach (var reference in references)
            {
                var score = project.Scores.First(s => s.AdaptedPart == reference.Page.Owner);
                var page = score.Pages.First(p => p.AdaptedPart == reference.Page);
                var section = page.Sections.First(s => s.AdaptedElement == reference.Section) as Elements.MusicSection;
                var interior = section.Interior as Elements.CWNotation.MusicSectionInterior;
                var system = interior.Systems.First(s => ((Model.Elements.CWNotation.System)s.AdaptedElement).Measures.Contains(measure));
                var staff = system.Staves.First(s => ((Model.Elements.CWNotation.Staff)s.AdaptedElement).Descriptor == staffDescriptor);

                removedSymbols.Add(
                    staff.Symbols.First(s => s is Elements.CWNotation.DurationalSymbol && 
                        (((Elements.CWNotation.DurationalSymbol)s).AdaptedElement as Model.Elements.CWNotation.DurationalSymbol).PresentedElement == removedElement
                        )
                        );
            }
        }

        #endregion
        
        #region Command Logic

        /// <inheritdoc/>
        protected override void CommandOperation(object parameter)
        {
            if (Symbol is Elements.CWNotation.Clef)
            {
                var modelStaff = Symbol.FindOwner<Elements.CWNotation.Staff>().AdaptedElement as Model.Elements.CWNotation.Staff;
                modelStaff.RemoveClef(Symbol.AdaptedElement as Model.Elements.CWNotation.Clef);

                if (modelStaff.FindOwner<Model.Elements.PageElement>() != null)
                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(modelStaff.FindOwner<Model.Elements.CWNotation.System>());
                else
                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(modelStaff.FindOwner<Model.Elements.CWNotation.System>());
            }
            else if (Symbol is Elements.CWNotation.DurationalSymbol)
            {
                var modelSymbol = ((Model.Elements.CWNotation.DurationalSymbol)Symbol.AdaptedElement);
                var modelComposition = modelSymbol.PresentedElement.FindOwner<Model.Elements.CompositionElement>();
                var modelPart = (Model.Packaging.CompositionPart)modelComposition.PackagePart;
                var measure = modelSymbol.Measure;

                modelPart.RaiseGlobalChangePending(measure, measure);
                StoreRemovedSymbols(modelSymbol.PresentedElement, Symbol.FindOwner<Elements.Project>());
                if (modelSymbol.PresentedElement.Connectors.Count > 0)
                {
                    var system = Symbol.FindOwner<Elements.CWNotation.System>();
                    var macro = new UndoableMacroCommand(UndoStack, RedoStack)
                    {
                        UseStacks = false
                    };

                    foreach (var connector in modelSymbol.PresentedElement.Connectors)
                    {
                        var pc = system.Connectors.First(c => ((Model.Elements.CWNotation.Connector)c.AdaptedElement).PresentedConnector == connector);
                        macro.Commands.Add(
                            new RemoveConnectorCommand(UndoStack, RedoStack)
                            {
                                Connector = pc
                            }
                            );
                    }

                    connectorCommand = macro;
                    connectorCommand.Execute(null);
                }
                modelSymbol.PresentedElement.FindOwner<Model.Elements.MusicalContentElement>().RemoveSymbol(modelSymbol.PresentedElement);

                var references = modelComposition.GetReferencingPages(measure, measure);

                foreach (var reference in references)
                {
                    var interior = reference.Section.Interior as Model.Elements.CWNotation.MusicSectionInterior;
                    var system = interior.Systems.First(s => s.Measures.Contains(measure));

                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(system);
                }

                var lvInterior = modelPart.LinearView.MusicSection.Interior as Model.Elements.CWNotation.MusicSectionInterior;
                Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(lvInterior.Systems[0]);
            }

            Symbol.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void RedoOperation()
        {
            if (Symbol is Elements.CWNotation.Clef)
            {
                var modelStaff = Symbol.FindOwner<Elements.CWNotation.Staff>().AdaptedElement as Model.Elements.CWNotation.Staff;
                modelStaff.RemoveClef(Symbol.AdaptedElement as Model.Elements.CWNotation.Clef);

                if (modelStaff.FindOwner<Model.Elements.PageElement>() != null)
                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(modelStaff.FindOwner<Model.Elements.CWNotation.System>());
                else
                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(modelStaff.FindOwner<Model.Elements.CWNotation.System>());
            }
            else if (Symbol is Elements.CWNotation.DurationalSymbol)
            {
                var modelSymbol = ((Model.Elements.CWNotation.DurationalSymbol)Symbol.AdaptedElement);
                var element = modelSymbol.PresentedElement;
                if (connectorCommand != null)
                    connectorCommand.Execute(null);
                element.FindOwner<Model.Elements.MusicalContentElement>().RemoveSymbol(element);

                foreach (var rs in removedSymbols)
                {
                    var modelRS = (Model.Elements.CWNotation.DurationalSymbol)rs.AdaptedElement;                 
                    var modelSystem = modelRS.FindOwner<Model.Elements.CWNotation.System>();
                    if (modelSystem.FindOwner<Model.Elements.PageElement>() != null)
                        Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(modelSystem);
                    else
                        Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(modelSystem);
                }
            }

            Symbol.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void UndoOperation()
        {
            if (Symbol is Elements.CWNotation.Clef)
            {
                var staff = Symbol.FindOwner<Elements.CWNotation.Staff>();
                var modelStaff = staff.AdaptedElement as Model.Elements.CWNotation.Staff;
                modelStaff.RestoreClef(Symbol.AdaptedElement as Model.Elements.CWNotation.Clef);

                if (modelStaff.FindOwner<Model.Elements.PageElement>() != null)
                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(modelStaff.FindOwner<Model.Elements.CWNotation.System>());
                else
                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(modelStaff.FindOwner<Model.Elements.CWNotation.System>());

                var vmClef = staff.Symbols.First(s => s.AdaptedElement == Symbol.AdaptedElement);
                staff.Symbols[staff.Symbols.IndexOf(vmClef)] = Symbol;
            }
            else if (Symbol is Elements.CWNotation.DurationalSymbol)
            {
                var modelSymbol = ((Model.Elements.CWNotation.DurationalSymbol)Symbol.AdaptedElement);
                var element = modelSymbol.PresentedElement;
                element.FindOwner<Model.Elements.MusicalContentElement>().AddExistingSymbol(element.FindOwner<Model.Elements.BeatElement>().Beginning, element);

                foreach (var rs in removedSymbols)
                {
                    var modelRS = (Model.Elements.CWNotation.DurationalSymbol)rs.AdaptedElement;
                    var modelStaff = modelRS.FindOwner<Model.Elements.CWNotation.Staff>();
                    modelStaff.RestoreSymbol(modelRS);

                    var staff = rs.FindOwner<Elements.CWNotation.Staff>();
                    var vmSymbol = staff.Symbols.First(s => s.AdaptedElement == modelRS);
                    staff.Symbols[staff.Symbols.IndexOf(vmSymbol)] = rs;

                    var modelSystem = modelStaff.FindOwner<Model.Elements.CWNotation.System>();
                    if (modelSystem.FindOwner<Model.Elements.PageElement>() != null)
                        Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(modelSystem);
                    else
                        Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(modelSystem);
                }

                if (connectorCommand != null)
                    connectorCommand.Execute(null);
            }

            Symbol.FindOwner<Elements.Project>().IsModified = true;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the removed symbol.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.CWNotation.CoreSymbol"/>
        /// </para>
        /// <para>
        /// The removed symbol.<br/>
        /// This property wraps the "Symbol" command parameter.
        /// </para>
        /// </value>
        public Elements.CWNotation.CoreSymbol Symbol
        {
            get { return GetParameterValue<Elements.CWNotation.CoreSymbol>("Symbol"); }
            set { SetParameterValue<Elements.CWNotation.CoreSymbol>("Symbol", value); }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the command that removed the possible connector.
        /// </summary>
        private UndoableCommandBase connectorCommand;
        /// <summary>
        /// Holds the list of removed core symbols.
        /// </summary>
        private List<Elements.CWNotation.CoreSymbol> removedSymbols;

        #endregion
    }
}
